package com.google.gwt.gwtpages.client.page.loader;

import java.util.HashMap;
import java.util.Map;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.gwtpages.client.page.GWTPage;
import com.google.gwt.gwtpages.client.page.GWTPageAttributes;
import com.google.gwt.gwtpages.client.page.GWTPagesPresenter;
import com.google.gwt.gwtpages.client.page.parameters.PageParameters;

/**
 * Page loaded that uses a separate async request to lazy-load each page when
 * requested
 * 
 * @author Joe Hudson
 * 
 */
public abstract class AsyncPageLoader implements GWTPagesLoader {

	private Map<String, PageLoader> pages = new HashMap<String, AsyncPageLoader.PageLoader>();
	private Map<String, GWTPageAttributes> metaData = new HashMap<String, GWTPageAttributes>();
	private Map<String, Boolean> keepInStack = new HashMap<String, Boolean>();

	public boolean isValidPageToken(String token) {
		return (null != pages.get(token));
	}

	/**
	 * Register a page
	 * 
	 * @param token
	 *            the page token
	 * @param pageLoader
	 *            the {@link PageLoader}
	 */
	public AsyncPageLoader registerPage(String token, PageLoader pageLoader) {
		return registerPage(token, pageLoader, null, false);
	}

	/**
	 * Register a page
	 * 
	 * @param token
	 *            the page token
	 * @param pageLoader
	 *            the {@link PageLoader}
	 * @param keepInStack
	 *            true to keep the page in the stack and false otherwise
	 */
	public AsyncPageLoader registerPage(String token, PageLoader pageLoader,
			boolean keepInStack) {
		return registerPage(token, pageLoader, null, keepInStack);
	}

	/**
	 * 
	 * Register a page
	 * 
	 * @param token
	 *            the page token
	 * @param pageLoader
	 *            the {@link PageLoader}
	 * @param metaData
	 *            the {@link GWTPageAttributes}
	 */
	public AsyncPageLoader registerPage(String token, PageLoader pageLoader,
			GWTPageAttributes metaData) {
		return registerPage(token, pageLoader, metaData, false);
	}

	/**
	 * 
	 * Register a page
	 * 
	 * @param token
	 *            the page token
	 * @param pageLoader
	 *            the {@link PageLoader}
	 * @param metaData
	 *            the {@link GWTPageAttributes}
	 * @param keepInStack
	 *            true to keep the page in the stack and false otherwise
	 */
	public AsyncPageLoader registerPage(String token, PageLoader pageLoader,
			GWTPageAttributes metaData, boolean keepInStack) {
		pages.put(token, pageLoader);
		if (null != metaData)
			this.metaData.put(token, metaData);
		this.keepInStack.put(token, keepInStack);
		return this;
	}

	public void getPage(final String pageToken,
			final PageLoadCallback pageLoadCallback) {
		final PageLoader loader = pages.get(pageToken);
		if (null != loader) {
			GWT.runAsync(new RunAsyncCallback() {

				public void onSuccess() {
					try {
						pageLoadCallback.onPageFound(new GWTPage(
								pageToken, loader.createPage(),
								getMetaData(pageToken), AsyncPageLoader.this));
					} catch (Throwable e) {
						pageLoadCallback.onPageLoadFailure(pageToken, e);
					}
				}

				public void onFailure(Throwable reason) {
					pageLoadCallback.onPageLoadFailure(pageToken, reason);
				}
			});
		} else {
			pageLoadCallback.onPageNotFound(pageToken);
		}
	}

	public boolean keepInStack(GWTPage pageData,
			PageParameters parameters) {
		Boolean rtn = this.keepInStack.get(pageData.getPageToken());
		if (null == rtn)
			return false;
		else
			return rtn;
	}

	/**
	 * Interface class used to define a class used to return the desired page
	 * instance. This is used so the actual page doesn't need to be associated
	 * with the page token so the page can be loaded asynchronously.
	 * 
	 * @author Joe Hudson
	 */
	public interface PageLoader {
		public GWTPagesPresenter createPage();
	}
}